package ca.quicko.webapp.html;

import javax.swing.text.html.HTML;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * Helper class to help generate small HTML strings. Usage resembles
 * StringBuffer's.
 * </p>
 * <p>
 * Here's a usage example: <blockquote>
 * 
 * <pre>
 * HtmlKit html = new HtmlKit();
 * html.addTagOpen(HTML.Tag.TEXTAREA);
 * html.addAttribute(HTML.Attribute.NAME, getProperty());
 * html.addAttribute(HTML.Attribute.ROWS, getRows());
 * html.addAttribute(HTML.Attribute.COLS, getCols());
 * html.addAttributesDone();
 * html.add(String.valueOf(lookupValue()));
 * html.addTagClose(HTML.Tag.TEXTAREA);
 * return html.toString();
 * </pre>
 * 
 * </blockquote>
 * <p>
 */
public class HtmlKit {

	private Log log = LogFactory.getLog(getClass());

	private StringBuffer sb;

	private static final String TAG_START = "<";

	private static final String TAG_CLOSE = "</";

	private static final String TAG_EMPTY = " />";

	private static final String TAG_END = ">";

	private QuickCheck check = null;

	/**
	 * Constructor that doesn't do validation
	 */
	public HtmlKit() {
		this.sb = new StringBuffer();
	}

	/**
	 * Constructor that helps do minimal bracket/tag validation. Validation
	 * occurs on toString()
	 * @param doQuickCheck
	 *            if <code>true</code> then perform validation
	 */
	public HtmlKit(boolean doQuickCheck) {
		if (doQuickCheck) {
			this.check = new QuickCheck();
		}
		new HtmlKit();
	}

	/**
	 * Deletes everything in this buffer. Bubye!
	 */
	public void clear() {
		this.sb = new StringBuffer();
	}

	/**
	 * If you have enabled validation and want to see the results, please see
	 * logs
	 * @return this.StringBuffer.toString()
	 */
	public String toString() {
		if (this.check != null && !this.check.isEven() && log.isWarnEnabled()) {
			log.warn(this.check.getStatus());
		}
		return this.sb.toString();
	}

	/**
	 * @return
	 */
	public HtmlKit addPageHeader() {
		this.sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		this.sb.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \n");
		this.sb.append("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
		this.sb.append("<html xmlns=\"http://www.w3.org/1999/xhtml\"><body>");
		return this;
	}
	
	/**
	 * @return
	 */
	public HtmlKit addPageFooter() {
		this.sb.append("</body></html>");
		return this;
	}
	
	/**
	 * Adds an opening bracket and then the tag name ( &lt;specified_tag )
	 * @param tag
	 *            the tag name to start
	 * @return this object
	 */
	public HtmlKit addTagOpen(HTML.Tag tag) {
		if (tag != null) {
			this.sb.append(HtmlKit.TAG_START);
			this.sb.append(tag);
			if (this.check != null) {
				this.check.openTag();
				this.check.openBracket();
			}
		}
		return this;
	}

	/**
	 * Adds the tag within brackets ( &lt;specified_tag&gt; )
	 * @param tag
	 *            the tag name to start
	 * @return this object
	 */
	public HtmlKit addTagOpenNoAttr(HTML.Tag tag) {
		return addTagOpen(tag).addAttributesDone();
	}

	/**
	 * Adds an attribute. It's suggested to use this method between addStart and
	 * addEnd so that the attributes are applied to a tag.
	 * @param name
	 *            attribute name
	 * @param value
	 *            value of the attribute. if value is null then nothing is added
	 * @return this object
	 */
	public HtmlKit addAttribute(HTML.Attribute name, String value) {
		addAttribute(String.valueOf(name), value);
		return this;
	}

	/**
	 * Adds an attribute. It's suggested to use this method between addStart and
	 * addEnd so that the attributes are applied to a tag.
	 * @param name
	 *            attribute name
	 * @param value
	 *            value of the attribute. if value is null then nothing is added
	 * @return this object
	 */
	public HtmlKit addAttribute(HtmlKit.Attribute name, String value) {
		addAttribute(String.valueOf(name), value);
		return this;
	}

	private HtmlKit addAttribute(String name, String value) {
		if (value != null) {
			this.sb.append(' ').append(name).append('=');
			this.sb.append('"').append(value).append('"');
		}
		return this;
	}

	/**
	 * Adds a closing bracket ( &gt; ).
	 * @return this object
	 */
	public HtmlKit addAttributesDone() {
		return this.closeOpeningTag(false);
	}

	/**
	 * Adds a closing bracket for an empty tag ( /&gt; ).
	 * @return this object
	 */
	public HtmlKit addTagCloseEmpty() {
		return this.closeOpeningTag(true);
	}

	/**
	 * Adds larger than bracket ( &gt; ). Adds a slash if isEmpty is true (
	 * /&gt; ).
	 * @param isEmpty
	 *            <code>true</code> if no closing tag is needed
	 * @return this object
	 */
	private HtmlKit closeOpeningTag(boolean isEmpty) {
		if (isEmpty) {
			this.sb.append(HtmlKit.TAG_EMPTY);
			if (this.check != null) {
				this.check.closeTag();
			}
		}
		else {
			this.sb.append(HtmlKit.TAG_END);
		}
		if (this.check != null) {
			this.check.closeBracket();
		}
		return this;
	}

	/**
	 * Adds a simple anchor tag with no class
	 * @param url
	 *            the link
	 * @param linkText
	 *            the text to show
	 * @return the anchor's open/close tags with text in between
	 */
	public HtmlKit addLink(String url, String linkText) {
		this.addTagOpen(HTML.Tag.A);
		this.addAttribute(HTML.Attribute.HREF, url);
		this.addAttributesDone();
		this.add(linkText);
		this.addTagClose(HTML.Tag.A);
		return this;
	}

	/**
	 * Adds a random string
	 * @param string
	 * @return this object
	 */
	public HtmlKit add(String string) {
		this.sb.append(string);
		return this;
	}

	/**
	 * Adds closing tag (e.g. - &lt;/specified_tag&gt; )
	 * @param tag
	 *            the one to close
	 * @return this object
	 */
	public HtmlKit addTagClose(HTML.Tag tag) {
		if (tag != null) {
			this.sb.append(HtmlKit.TAG_CLOSE);
			this.sb.append(tag);
			this.sb.append(HtmlKit.TAG_END);
		}
		if (this.check != null) {
			this.check.closeTag();
		}
		return this;
	}

	/**
	 * Typesafe enumeration for HTML tags that are not in Java's HTML.Tag class
	 */
	public static class Tag extends HTML.Tag {

		private String name;

		public Tag(String name) {
			this.name = name;
		}

		public String toString() {
			return this.name;
		}

		public static final Tag LABEL = new Tag("label");

		public static final Tag OPTGROUP = new Tag("optgroup");

		public static final Tag FIELDSET = new Tag("fieldset");

		public static final Tag LEGEND = new Tag("legend");
	}

	/**
	 * Typesafe enumeration for HTML tags that are not in Java's HTML.Tag class
	 */
	public static class Attribute {

		private String name;

		public Attribute(String name) {
			this.name = name;
		}

		public String toString() {
			return this.name;
		}

		public static final Attribute ACCESSKEY = new Attribute("accesskey");

		public static final Attribute DISABLED = new Attribute("disabled");

		public static final Attribute FOR = new Attribute("for");

		public static final Attribute LABEL = new Attribute("label");

		public static final Attribute READONLY = new Attribute("readonly");

		public static final Attribute TABINDEX = new Attribute("tabindex");

		public static final Attribute TABLE_SUMMARY = new Attribute(
				"tableSummary");

		public static final Attribute XML_LANG = new Attribute("xml:lang");
	}

	/**
	 * Small class to keep HtmlHelper user informed about unclosed brackets/tags
	 * @author Eric Landry
	 */
	class QuickCheck {

		int bracketCount = 0;

		int tagCount = 0;

		public void openBracket() {
			this.bracketCount++;
		}

		public void closeBracket() {
			this.bracketCount--;
		}

		public void openTag() {
			this.tagCount++;
		}

		public void closeTag() {
			this.tagCount--;
		}

		/**
		 * @return <code>true</code> if all tags/brackets were correctly
		 *         closed
		 */
		public boolean isEven() {
			if (this.bracketCount == 0 && this.tagCount == 0) {
				return true;
			}
			return false;
		}

		/**
		 * @return a String containing the result of the quick check
		 */
		public String getStatus() {
			StringBuffer sb = new StringBuffer();
			if (this.bracketCount == 0) {
				sb.append("All brackets seemed to have been closed. ");
			}
			else {
				sb.append("Number of brackets unclosed: ").append(
						this.bracketCount).append(". ");
			}
			if (this.tagCount == 0) {
				sb.append("All tags seemed to have been closed. ");
			}
			else {
				sb.append("Number of tags unclosed: ").append(this.tagCount)
						.append('.');
			}
			return sb.toString();
		}
	}
}
